//
// Create by Zeng Yun on 2018/12/22
//

package home

import (
	"adai.design/jarvis/common/log"
	"adai.design/jarvis/device"
	"adai.design/jarvis/home/model"
	"adai.design/jarvis/member"
	"encoding/json"
	"errors"
	"fmt"
	"time"
)

type ContainerManager struct {
	containers []*model.Container
}

// 查找成员
func (cs *ContainerManager) findContainerById(id string) (*model.Container, error) {
	for _, v := range cs.containers {
		if v.Id == id {
			return v, nil
		}
	}
	return nil, fmt.Errorf("container(%s) 404 not found", id)
}

func (cs *ContainerManager) findContainerByAid(id string) (string, error) {
	for _, c := range cs.containers {
		for _, a := range c.Accessories {
			if a.UUID == id {
				return c.Id, nil
			}
		}
	}
	return "", errors.New("404 not found")
}

// 执行动作
func (cs *ContainerManager) executeActions(h *Home, as ...*model.Action) error {
	// 将动作根据容器进行分组
	groups := make(map[string][]*model.Action)
	for _, a := range as {
		cid, err := cs.findContainerByAid(a.AId)
		if err == nil {
			if g, ok := groups[cid]; ok {
				groups[cid] = append(g, a)
			} else {
				groups[cid] = []*model.Action{a}
			}
		}
	}

	var result error = nil

	// 将消息发送给不同的wifi设备
	for k, v := range groups {
		buf, _ := json.Marshal(v)
		msg := &device.Message{
			Path:   device.MsgPathContainerCharacteristic,
			Method: device.MsgMethodPost,
			Data:   buf,
		}

		container, err := cs.findContainerById(k)
		if err != nil || container.Reachable == false {
			result = fmt.Errorf("failed")
		}
		h.postMessageToContainer(k, msg)
	}
	return result
}

// 获取全部的容器信息
func (cs *ContainerManager) handleMemberContainers(h *Home, pkg *member.MessagePkg) error {
	if pkg.Msg.Method == member.MsgMethodGet {
		buf, _ := json.Marshal(cs.containers)
		ack := &member.MessagePkg{
			Type: pkg.Type,
			Ctx:  pkg.Ctx,
			Msg: &member.Message{
				Path:   pathContainer,
				Method: member.MsgMethodGet,
				State:  "ok",
				Home:   h.instance.Id,
				Data:   buf,
			},
		}
		h.replyMemberResult(ack)
	}
	return nil
}

// 获取容器的属性信息
func (cs *ContainerManager) handleMemberContainerCharacteristic(h *Home, pkg *member.MessagePkg) error {
	if pkg.Msg.Method == member.MsgMethodGet {
		var ccs []*model.ContainerCharacteristic
		for _, c := range cs.containers {
			cs := c.GetContainerCharacteristic()
			ccs = append(ccs, cs)
		}
		data, _ := json.Marshal(ccs)
		ack := &member.MessagePkg{
			Type: pkg.Type,
			Ctx:  pkg.Ctx,
			Msg: &member.Message{
				Path:   pathContainerCharacteristic,
				Method: member.MsgMethodGet,
				State:  "ok",
				Home:   h.instance.Id,
				Data:   data,
			},
		}
		h.replyMemberResult(ack)
	}
	return nil
}

// 容器属性操作
func (cs *ContainerManager) handleMemberCharacteristic(h *Home, pkg *member.MessagePkg) error {
	if pkg.Msg.Method == member.MsgMethodPost {
		var actions []*model.Action
		err := json.Unmarshal(pkg.Msg.Data, &actions)
		if err == nil {
			cs.executeActions(h, actions...)
		}
	}
	return nil
}

func (cs *ContainerManager) handleMemberPkg(h *Home, pkg *member.MessagePkg) error {
	switch pkg.Msg.Path {
	case pathContainer:
		return cs.handleMemberContainers(h, pkg)

	case pathContainerCharacteristic:
		return cs.handleMemberContainerCharacteristic(h, pkg)
	}
	return nil
}

// 处理容器发送过来的状态更新
func (cs *ContainerManager) handleDeviceContainerInfo(h *Home, pkg *device.MessagePkg) error {
	// 设备在线
	if pkg.Msg.Method == device.MsgMethodPut && pkg.Msg.State == device.ContainerStateOnline {
		post := &device.Message{
			Path:   device.MsgPathContainerInfo,
			Method: device.MsgMethodGet,
		}
		h.updateAccessoryState(pkg.DevId, model.TypeZigbeeCoordinator, device.ContainerStateOnline)
		return h.postMessageToContainer(pkg.DevId, post)
	}

	// 设备离线
	if pkg.Msg.Method == device.MsgMethodPut && pkg.Msg.State == device.ContainerStateOffline {
		container, err := cs.findContainerById(pkg.DevId)
		if err != nil {
			return err
		}
		container.Reachable = false
		for _, a := range container.Accessories {
			a.IsReachable = false
		}

		// 向家庭成员推送设备离线消息
		cchars := &model.ContainerCharacteristic{
			Id:          pkg.DevId,
			Version:     container.Version,
			IsReachable: false,
		}
		put := &member.Message{
			Path:   pathContainerCharacteristic,
			Method: device.MsgMethodPut,
		}
		h.updateAccessoryState(pkg.DevId, model.TypeZigbeeCoordinator, device.ContainerStateOffline)
		put.Data, _ = json.Marshal(cchars)
		return h.putMessageToMembers(put)
	}

	// 查看版本是否需要更新
	if pkg.Msg.Method == device.MsgMethodGet || pkg.Msg.Method == device.MsgMethodPut {
		info := struct {
			Version int `json:"version"`
		}{}
		if err := json.Unmarshal(pkg.Msg.Data, &info); err != nil {
			return err
		}

		container, _ := cs.findContainerById(pkg.DevId)
		// 设备不存在 或 服务器保存的设备版本不等于设备实际的版本
		// 从新获取设备信息
		if container == nil || container.Version != info.Version {
			if container != nil {
				log.Debug("container(%s) version(%d:%d)", pkg.DevId, info.Version, container.Version)
			}
			msg := &device.Message{
				Path:   device.MsgPathContainer,
				Method: device.MsgMethodGet,
			}
			return h.postMessageToContainer(pkg.DevId, msg)
		} else {
			// 服务器保存的设备版本等于现在设备的版本
			msg := &device.Message{
				Path:   device.MsgPathContainerCharacteristic,
				Method: device.MsgMethodGet,
			}
			return h.postMessageToContainer(pkg.DevId, msg)
		}
	}
	return nil
}

// 容器信息更新
func (cs *ContainerManager) handleDeviceContainer(h *Home, pkg *device.MessagePkg) error {
	if pkg.Msg.Method == device.MsgMethodGet {
		var container model.Container
		err := json.Unmarshal(pkg.Msg.Data, &container)
		if err != nil {
			return err
		}

		// 将新的容器信息保存之家庭家庭数据中
		for i, v := range cs.containers {
			if v.Id == container.Id {
				cs.containers = append(cs.containers[:i], cs.containers[i+1:]...)
			}
		}

		cs.containers = append(cs.containers, &container)
		container.Reachable = true

		h.Save()
		put := &member.Message{
			Path:   pathContainerCharacteristic,
			Method: member.MsgMethodPut,
		}
		put.Data, _ = json.Marshal(container.GetContainerCharacteristic())
		return h.putMessageToMembers(put)
	}
	return nil
}

// 容器属性更新
func (cs *ContainerManager) handleDeviceContainerCharacteristic(h *Home, pkg *device.MessagePkg) error {
	if pkg.Msg.Method == device.MsgMethodGet {
		var chars []*model.Characteristic
		if err := json.Unmarshal(pkg.Msg.Data, &chars); err != nil {
			return err
		}
		container, err := cs.findContainerById(pkg.DevId)
		if err != nil {
			return err
		}
		container.RefreshByCharacteristics(chars...)
		put := &member.Message{
			Path:   pathContainerCharacteristic,
			Method: member.MsgMethodPut,
		}
		put.Data, _ = json.Marshal(container.GetContainerCharacteristic())
		return h.putMessageToMembers(put)
	}

	if pkg.Msg.Method == device.MsgMethodPut {
		var chars []*model.Characteristic
		if err := json.Unmarshal(pkg.Msg.Data, &chars); err != nil {
			return err
		}
		container, err := cs.findContainerById(pkg.DevId)
		if err != nil {
			return err
		}
		container.UpdateCharacteristics(chars...)
		cs.saveCharacteristicLog(h, container, chars...)
		h.updateCharacteristic(chars...)
		return nil
	}
	return nil
}

// 保存属性变化记录
func (cs *ContainerManager) saveCharacteristicLog(h *Home, container *model.Container, chars ...*model.Characteristic) {
	for _, char := range chars {
		switch container.GetCharacteristicType(char) {
		case model.HMCharacteristicTypeCurrentTemperature,
			model.HMCharacteristicTypeCurrentRelativeHumidity,
			model.HMCharacteristicTypeCurrentAmbientLightLevel,
			model.HMCharacteristicTypeContactSensorState,
			model.HMCharacteristicTypeOn:
			info := &characteristicMarkCell{
				Home:           h.instance.Id,
				Accessory:      char.AId,
				Service:        char.SId,
				Characteristic: char.CId,
				Value:          char.Value,
				Time:           time.Now(),
			}
			_ = insertCharacteristicLog(info)
		}
	}
}

// 处理设备容器发送过来的消息
func (cs *ContainerManager) handleContainerPkg(h *Home, pkg *device.MessagePkg) error {
	switch pkg.Msg.Path {
	// 设备在线\离线\添加\删除\版本更新
	case device.MsgPathContainerInfo:
		return cs.handleDeviceContainerInfo(h, pkg)

	// 设备信息变更
	// 例如: 子设备的添加和删除
	case device.MsgPathContainer:
		return cs.handleDeviceContainer(h, pkg)

	// 设备属性变更
	case device.MsgPathContainerCharacteristic:
		return cs.handleDeviceContainerCharacteristic(h, pkg)
	}

	return nil
}
